package org.shagin.jfin.ui.sheet;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.shagin.jfin.core.Debug;
import org.shagin.jfin.data.model.Account;
import org.shagin.jfin.data.model.IBudget;
import org.shagin.jfin.data.model.OperationCategory;
import org.shagin.jfin.data.model.OperationCategoryLeaf;
import org.shagin.jfin.data.model.OperationRule;
import org.shagin.jfin.model.event.AccountEvent;
import org.shagin.jfin.model.event.BudgetAdapter;
import org.shagin.jfin.model.event.OperationRuleEvent;
import org.shagin.jfin.sheet.Processor;
import org.shagin.jfin.sheet.Sheet;
import org.shagin.jfin.sheet.Sheet.CostCenterColumnSet;
import org.shagin.jfin.sheet.Sheet.CostCenterColumnSetEx;
import org.shagin.jfin.sheet.SheetCellRenderer;
import org.shagin.jfin.sheet.SheetColumn;
import org.shagin.jfin.sheet.SheetColumnCategory;
import org.shagin.jfin.sheet.SheetTableModel;
import org.shagin.jfin.ui.date.DateRangeEvent;
import org.shagin.jfin.ui.date.DateRangeListener;
import org.shagin.jfin.ui.date.JDateRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.crionics.ghtable.ColumnGroup;
import com.crionics.ghtable.GroupableTableHeader;
import com.crionics.ghtable.JGHFixedColumnTablePanel;

/**
 * The Class SheetTable.
 */
public class SheetTableView extends JPanel {
    private static final String OPERATION_CATEGORY_PROPERTY = "operationCategory";
    private static final Logger log = LoggerFactory
            .getLogger(SheetTableView.class);
    private static final long serialVersionUID = -1449574517568863257L;
    private SheetTableModel model;
    private Sheet sheet;
    private JGHFixedColumnTablePanel tablePanel;
    private JDateRange dateRange;
    private IBudget budget;
    
    private ViewBudgetListener viewBudgetListener = new ViewBudgetListener();
    private DateRangeListener dateRangeListener;

    /**
     * The listener interface for receiving viewBudget events. The class that is
     * interested in processing a viewBudget event implements this interface,
     * and the object created with that class is registered with a component
     * using the component's <code>addBudgetListener<code> method. When
     * the viewBudget event occurs, that object's appropriate
     * method is invoked.
     * 
     * @see AccountEvent
     */
    private class ViewBudgetListener extends BudgetAdapter {

        /**
         * {@inheritDoc}
         */
        @Override
        public void accountChanged(AccountEvent e) {
            Debug.traceEnter(log);
            recalculate();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void operationRuleChanged(OperationRuleEvent e) {
            Debug.traceEnter(log);
            recalculate();
        }
    }

    /**
     * The listener interface for receiving viewContainer events. The class that
     * is interested in processing a viewContainer event implements this
     * interface, and the object created with that class is registered with a
     * component using the component's
     * <code>addContainerListener<code> method. When
     * the viewContainer event occurs, that object's appropriate
     * method is invoked.
     * 
     * @see ContainerEvent
     */
    private class ViewContainerListener extends ContainerAdapter {

        /**
         * {@inheritDoc}
         */
        @Override
        public void componentRemoved(ContainerEvent e) {
            Debug.traceEnter(log);
            removeEventListeners();
        }
    }

    /**
     * Instantiates a new sheet table.
     */
    public SheetTableView() {
        Debug.traceEnter(log);
        setLayout(new BorderLayout());
        tablePanel = new JGHFixedColumnTablePanel();
        add(createToolbarPanel(), BorderLayout.NORTH);
        add(tablePanel, BorderLayout.CENTER);
        addContainerListener(new ViewContainerListener());
        initEventListeners();
    }

    /**
     * Creates the toolbar panel.
     * 
     * @return the j panel
     */
    public JPanel createToolbarPanel() {
        JPanel toolbarPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        dateRange = new JDateRange();
        dateRange.setOpenDatesEnabled(false);
        toolbarPanel.add(dateRange);
        return toolbarPanel;
    }

    /**
     * Recalculate.
     */
    public void recalculate() {
        Debug.traceEnter(log);
        setBudget(getBudget());
    }

    /**
     * Sets the data source.
     * 
     * @param budget the new budget
     */
    public void setBudget(IBudget budget) {
        Debug.traceEnter(log);
        removeEventListeners();
        this.budget = budget;
        dateRange.setDateRange(budget.getDateRange());
        Processor p = new Processor(budget);
        sheet = p.execute();
        model = new SheetTableModel(sheet);
        tablePanel.setModel(model);
        
        List<ColumnGroup> groups = new ArrayList<ColumnGroup>();
        groups.addAll(getColumnGroups(budget.getOperationRuleDomain()
                .getIncomes()));
        groups.addAll(getColumnGroups(budget.getOperationRuleDomain()
                .getExpenses()));
        groups.addAll(getCostCentersColumnGroups());
        setupSaldoGroup(sheet.getTotalSaldo(), null);
        
        GroupableTableHeader header = tablePanel.getMainTable()
                .getTableHeader();
        for (ColumnGroup columnGroup : groups)
            header.addColumnGroup(columnGroup);

        SheetCellRenderer sheetCellRenderer = new SheetCellRenderer(model);
        tablePanel.getMainTable().setDefaultRenderer(Object.class,
                sheetCellRenderer);
        tablePanel.getFixedTable().setDefaultRenderer(Object.class,
                sheetCellRenderer);

        addEventListeners();
    }

    /**
     * Gets the column groups.
     * 
     * @param rules the rules
     * @return the column groups
     */
    private List<ColumnGroup> getColumnGroups(List<OperationRule> rules) {
        List<ColumnGroup> result = new ArrayList<ColumnGroup>();

        for (OperationCategory root : OperationRule.getRoots(rules))
            result.add(addOperationCategoryColumnGroup(null, root));

        return result;
    }

    /**
     * Gets the cost centers column groups.
     * 
     * @return the cost centers column groups
     */
    private List<ColumnGroup> getCostCentersColumnGroups() {
        List<ColumnGroup> result = new ArrayList<ColumnGroup>();
        TableColumnModel cm = tablePanel.getMainTable().getColumnModel();
        for (CostCenterColumnSetEx ccc : sheet.getCostCenters()) {
            ColumnGroup cg = new ColumnGroup(ccc.costCenter.getId());
            // TODO Localize
            ColumnGroup cgAcc = new ColumnGroup("Счет");
            cg.add(cgAcc);
            TableColumn tableColumn;
            for (Account account : ccc.accounts.keySet()) {
                tableColumn = cm.getColumn(sheet.getData().getColumnIndex(
                        ccc.accounts.get(account)));
                cgAcc.add(tableColumn);
            }
            setupSaldoGroup(ccc, cg);
            result.add(cg);
        }
        return result;
    }

    /**
     * Setup saldo group.
     * 
     * @param ccc the ccc
     * @param cg the cg
     */
    private void setupSaldoGroup(CostCenterColumnSet ccc, ColumnGroup cg) {
        TableColumnModel cm = tablePanel.getMainTable().getColumnModel();
        TableColumn tableColumn;
        tableColumn = cm.getColumn(sheet.getData().getColumnIndex(
                ccc.colExpense));
        // TODO Localize
        tableColumn.setHeaderValue("РАСХОДЫ");
        if (null != cg)
            cg.add(tableColumn);
        tableColumn = cm.getColumn(sheet.getData()
                .getColumnIndex(ccc.colIncome));
        // TODO Localize
        tableColumn.setHeaderValue("ДОХОДЫ");
        if (null != cg)
            cg.add(tableColumn);
        tableColumn = cm
                .getColumn(sheet.getData().getColumnIndex(ccc.colSaldo));
        // TODO Localize
        tableColumn.setHeaderValue("САЛЬДО");
        if (null != cg)
            cg.add(tableColumn);
    }

    /**
     * Adds the operation category column group.
     * 
     * @param parent the parent
     * @param cat the cat
     * @return the column group
     */
    private ColumnGroup addOperationCategoryColumnGroup(ColumnGroup parent,
            OperationCategory cat) {
        ColumnGroup cg = new ColumnGroup(cat.getId());

        cg.setClientProperty(OPERATION_CATEGORY_PROPERTY, cat);

        if (null != parent) {
            parent.add(cg);
        }

        if (cat instanceof OperationCategoryLeaf) {
            OperationCategoryLeaf c = (OperationCategoryLeaf) cat;
            TableColumnModel cm = tablePanel.getMainTable().getColumnModel();
            for (OperationRule rule : c.getRules()) {
                TableColumn tableColumn = cm.getColumn(model.findColumn(rule
                        .getId()));
                cg.add(tableColumn);
            }
            setSumColumn(cg);
        } else
            for (OperationCategory child : cat.getChilds())
                addOperationCategoryColumnGroup(cg, child);

        return cg;
    }

    /**
     * Find sum column.
     * 
     * @param category the category
     * @return the table column
     */
    private TableColumn findSumColumn(OperationCategory category) {
        TableColumn result = null;
        TableColumnModel cm = tablePanel.getMainTable().getColumnModel();
        for (int i = 0; i < model.getColumnCount(); i++) {
            SheetColumn<?> col = sheet.getData().getColumn(i);
            if (col instanceof SheetColumnCategory) {
                SheetColumnCategory colCategory = (SheetColumnCategory) col;
                if (colCategory.getCategory() == category) {
                    result = cm.getColumn(i);
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Sets the sum column.
     * 
     * @param columnGroup the new sum column
     */
    private void setSumColumn(ColumnGroup columnGroup) {
        if (null == columnGroup)
            return;
        OperationCategory operationCategory = (OperationCategory) columnGroup
                .getClientProperty(OPERATION_CATEGORY_PROPERTY);
        TableColumn sumColumn = findSumColumn(operationCategory);
        columnGroup.add(sumColumn);
        setSumColumn(columnGroup.getParent());
    }

    /**
     * Gets the data source.
     * 
     * @return the data source
     */
    public IBudget getBudget() {
        return budget;
    }

    /**
     * Removes the events.
     */
    public void removeEventListeners() {
        if (budget != null) {
            budget.removeBudgetListener(viewBudgetListener);
        }
        dateRange.removeChangeListener(dateRangeListener);
    }

    /**
     * Adds the events.
     */
    public void addEventListeners() {
        if (budget != null) {
            budget.addBudgetListener(viewBudgetListener);
        }
        dateRange.addChangeListener(dateRangeListener);
    }

    /**
     * Inits the event listeners.
     */
    public void initEventListeners() {
        viewBudgetListener = new ViewBudgetListener();
        dateRangeListener = new DateRangeListener() {

            @Override
            public void dateRangeChanged(DateRangeEvent e) {
                Debug.traceEnter(log);
                recalculate();
            }
        };
    }

}
